/********************************************************************+
# Copyright 2018-2019 Daniel 'grindhold' Brendle
#
# This file is part of phex.
#
# phex is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, either
# version 3 of the License, or (at your option) any later
# version.
#
# phex is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with phex.
# If not, see http://www.gnu.org/licenses/.
*********************************************************************/

#include "graph_view.h"

void _generate_vertices(GLData* gld) {
    Phexdata* ctx = gld->ctx;
    gint64* coords = malloc(ctx->coord_len*sizeof(gint64));
    gint64* coord_param = malloc(ctx->coord_len*sizeof(gint64));
    for (int i = 0; i < ctx->coord_len; i++) {
        *(coord_param+i) = (double)elm_slider_value_get(ctx->coord_sliders[i]);
    }

    memcpy(coords, coord_param, sizeof(gint64)*ctx->coord_len);

    if (elm_radio_value_get(ctx->radio_x) != elm_radio_value_get(ctx->radio_y)) {
        coord_param[elm_radio_value_get(ctx->radio_x)] = PHEXFILE_SIMULATION_X_AXIS;
        coord_param[elm_radio_value_get(ctx->radio_y)] = PHEXFILE_SIMULATION_Y_AXIS;
    } else {
        // TODO: this is dumb default behaviour.
        //       Better forbid invalid input somehow
        coord_param[0] = PHEXFILE_SIMULATION_X_AXIS;
        coord_param[1] = PHEXFILE_SIMULATION_Y_AXIS;
    }

    PhexfileSimulation* sim = ctx->sim;
    int data_len = 0;
    GError* err = NULL;
    gdouble* data = NULL;

    GList* methods = phexfile_simulation_get_methods(sim);
    gchar* method = g_list_nth_data(methods, elm_radio_value_get(ctx->radio_method));

    data = phexfile_simulation_get_energies(sim, coord_param, ctx->coord_len, method, &data_len, &err);
    if (err != NULL) {
        g_error(err->message);
    }

    PhexfileCoordinate* x_coord;
    PhexfileCoordinate* y_coord;
    if (elm_radio_value_get(ctx->radio_x) != elm_radio_value_get(ctx->radio_y)) {
        x_coord = g_list_nth_data(phexfile_simulation_get_coordinates(sim), elm_radio_value_get(ctx->radio_x));
        y_coord = g_list_nth_data(phexfile_simulation_get_coordinates(sim), elm_radio_value_get(ctx->radio_y));
    } else {
        x_coord = g_list_nth_data(phexfile_simulation_get_coordinates(sim), 0);
        y_coord = g_list_nth_data(phexfile_simulation_get_coordinates(sim), 1);
    }
    unsigned int n_exc = g_list_length(phexfile_simulation_get_excitations(sim));

    unsigned int x_coord_steps = phexfile_coordinate_get_steps(x_coord);
    unsigned int y_coord_steps = phexfile_coordinate_get_steps(y_coord);

    double zeroplane_dist = phexfile_simulation_get_minimum_energy(sim) + 0.5;

    float* result = NULL;
    float* colors = NULL;

    gld->vertices_size = (n_exc * (x_coord_steps - 1) * (y_coord_steps -1) * 6 * 3 * sizeof(float))
          + (n_exc * (x_coord_steps - 1 + y_coord_steps -1) * 2 * 3 * sizeof(float));
    gld->colors_size = (n_exc * (x_coord_steps - 1) * (y_coord_steps -1) * 6 * 4 * sizeof(float))
          + (n_exc * (x_coord_steps - 1 + y_coord_steps -1) * 2 * 4 * sizeof(float));
    result = realloc(gld->vertices, (n_exc * (x_coord_steps - 1) * (y_coord_steps -1) * 6 * 3 * sizeof(float)  )
                        + (n_exc * (x_coord_steps - 1 + y_coord_steps -1) * 2 * 3 * sizeof(float)));
    colors = realloc(gld->colors, (n_exc * (x_coord_steps - 1) * (y_coord_steps -1) * 6 * 4 * sizeof(float)  )
                        + (n_exc * (x_coord_steps - 1 + y_coord_steps -1) * 2 * 4 * sizeof(float)));

    gld->vertices = result;
    gld->colors = colors;

    unsigned int res_offset = 0;
    unsigned int col_offset = 0;
    // Draw grids
    for (unsigned int e = 0; e < n_exc; e++) {
        for (unsigned int x = 0; x < x_coord_steps - 1; x++) {
            for (unsigned int y = 0; y < y_coord_steps - 1; y++) {
                float uly = (float)*(data + x * y_coord_steps * n_exc + y * n_exc + e) - zeroplane_dist;
                float ury = (float)*(data + x * y_coord_steps * n_exc + (y + 1)* n_exc + e) - zeroplane_dist;
                float lly = (float)*(data + (x+1) * y_coord_steps * n_exc + y * n_exc + e) - zeroplane_dist;
                float lry = (float)*(data + (x+1) * y_coord_steps * n_exc + (y+1) * n_exc  + e) - zeroplane_dist;

                int vio = 0;
                int cio = 0;

                if (isnan(uly) || isnan(ury) || isnan(lly) || isnan(lry)) {
                    for (int i = 0; i < 18; i++) {
                        *(result + res_offset + i) = FLT_MAX;
                    }
                    for (int i = 0; i < 24; i++) {
                        *(colors + col_offset + i) = FLT_MAX;
                    }
                    res_offset += 18;
                    col_offset += 24;
                    continue;
                }

                *(result + res_offset + vio++) = (1.0f/(y_coord_steps-1)) * y - 0.5f;
                *(result + res_offset + vio++) = (float) uly;
                *(result + res_offset + vio++) = (1.0f/(x_coord_steps-1)) * x - 0.5f;

                *(result + res_offset + vio++) = (1.0f/(y_coord_steps-1)) * (y+1) - 0.5f;
                *(result + res_offset + vio++) = (float) ury;
                *(result + res_offset + vio++) = (1.0f/(x_coord_steps-1)) * x - 0.5f;

                *(result + res_offset + vio++) = (1.0f/(y_coord_steps-1)) * y - 0.5f;
                *(result + res_offset + vio++) = (float) lly;
                *(result + res_offset + vio++) = (1.0f/(x_coord_steps-1)) * (x+1) - 0.5f;

                *(result + res_offset + vio++) = (1.0f/(y_coord_steps-1)) * y - 0.5f;
                *(result + res_offset + vio++) = (float) lly;
                *(result + res_offset + vio++) = (1.0f/(x_coord_steps-1)) * (x+1) - 0.5f;

                *(result + res_offset + vio++) = (1.0f/(y_coord_steps-1)) * (y+1) - 0.5f;
                *(result + res_offset + vio++) = (float) ury;
                *(result + res_offset + vio++) = (1.0f/(x_coord_steps-1)) * x - 0.5f;

                *(result + res_offset + vio++) = (1.0f/(y_coord_steps-1)) * (y+1) - 0.5f;
                *(result + res_offset + vio++) = (float) lry;
                *(result + res_offset + vio++) = (1.0f/(x_coord_steps-1)) * (x+1) - 0.5f;

                *(colors + col_offset + cio++) = palette[e*4]   -0.1f;
                *(colors + col_offset + cio++) = palette[e*4+1] -0.1f;
                *(colors + col_offset + cio++) = palette[e*4+2] -0.1f;
                *(colors + col_offset + cio++) = palette[e*4+3];

                *(colors + col_offset + cio++) = palette[e*4] ;
                *(colors + col_offset + cio++) = palette[e*4+1];
                *(colors + col_offset + cio++) = palette[e*4+2];
                *(colors + col_offset + cio++) = palette[e*4+3];

                *(colors + col_offset + cio++) = palette[e*4];
                *(colors + col_offset + cio++) = palette[e*4+1];
                *(colors + col_offset + cio++) = palette[e*4+2];
                *(colors + col_offset + cio++) = palette[e*4+3];

                *(colors + col_offset + cio++) = palette[e*4]   ;
                *(colors + col_offset + cio++) = palette[e*4+1] ;
                *(colors + col_offset + cio++) = palette[e*4+2];
                *(colors + col_offset + cio++) = palette[e*4+3];

                *(colors + col_offset + cio++) = palette[e*4] ;
                *(colors + col_offset + cio++) = palette[e*4+1];
                *(colors + col_offset + cio++) = palette[e*4+2];
                *(colors + col_offset + cio++) = palette[e*4+3];

                *(colors + col_offset + cio++) = palette[e*4]   +0.1f;
                *(colors + col_offset + cio++) = palette[e*4+1] +0.1f;
                *(colors + col_offset + cio++) = palette[e*4+2] +0.1f;
                *(colors + col_offset + cio++) = palette[e*4+3] +0.1f;

                res_offset += 18;
                col_offset += 24;
            }
        }
    }

    // Draw lines
    for (unsigned int e = 0; e < n_exc; e++) {
        for (unsigned int x = 0; x < x_coord_steps - 1; x++) {
                gint64 y = coords[elm_radio_value_get(ctx->radio_y)];
                float beg = (float)*(data + x * y_coord_steps * n_exc + y * n_exc + e) - zeroplane_dist;
                float end = (float)*(data + (x+1) * y_coord_steps * n_exc + y * n_exc + e) - zeroplane_dist;
                if (isnan(beg) || isnan(end)) {
                    for (int i = 0; i < 6; i++) {
                        *(result + res_offset + i) = FLT_MAX;
                    }
                    for (int i = 0; i < 8; i++) {
                        *(colors + col_offset + i) = FLT_MAX;
                    }
                    res_offset += 6;
                    col_offset += 8;
                    continue;
                }
                int vio = 0;
                int cio = 0;

                *(result + res_offset + vio++) = (1.0f/(y_coord_steps-1)) * y - 0.5f;
                *(result + res_offset + vio++) = (float) beg;
                *(result + res_offset + vio++) = (1.0f/(x_coord_steps-1)) * x - 0.5f;

                *(result + res_offset + vio++) = (1.0f/(y_coord_steps-1)) * y - 0.5f;
                *(result + res_offset + vio++) = (float) end;
                *(result + res_offset + vio++) = (1.0f/(x_coord_steps-1)) * (x+1) - 0.5f;

                *(colors + col_offset + cio++) = palette[e*4]  ;
                *(colors + col_offset + cio++) = palette[e*4+1];
                *(colors + col_offset + cio++) = palette[e*4+2];
                *(colors + col_offset + cio++) = 1.0;

                *(colors + col_offset + cio++) = palette[e*4]  ;
                *(colors + col_offset + cio++) = palette[e*4+1];
                *(colors + col_offset + cio++) = palette[e*4+2];
                *(colors + col_offset + cio++) = 1.0;

                res_offset += 6;
                col_offset += 8;
        }
        for (unsigned int y = 0; y < y_coord_steps - 1; y++) {
                gint64 x = coords[elm_radio_value_get(ctx->radio_x)];
                float beg = (float)*(data + x * y_coord_steps * n_exc + y * n_exc + e) - zeroplane_dist;
                float end = (float)*(data + x * y_coord_steps * n_exc + (y + 1)* n_exc + e) - zeroplane_dist;
                if (isnan(beg) || isnan(end)) {
                    for (int i = 0; i < 6; i++) {
                        *(result + res_offset + i) = FLT_MAX;
                    }
                    for (int i = 0; i < 8; i++) {
                        *(colors + col_offset + i) = FLT_MAX;
                    }
                    res_offset += 6;
                    col_offset += 8;
                    continue;
                }
                int vio = 0;
                int cio = 0;

                *(result + res_offset + vio++) = (1.0f/(y_coord_steps-1)) * y - 0.5f;
                *(result + res_offset + vio++) = (float) beg;
                *(result + res_offset + vio++) = (1.0f/(x_coord_steps-1)) * x - 0.5f;

                *(result + res_offset + vio++) = (1.0f/(y_coord_steps-1)) * (y+1) - 0.5f;
                *(result + res_offset + vio++) = (float) end;
                *(result + res_offset + vio++) = (1.0f/(x_coord_steps-1)) * x - 0.5f;

                *(colors + col_offset + cio++) = palette[e*4]  ;
                *(colors + col_offset + cio++) = palette[e*4+1];
                *(colors + col_offset + cio++) = palette[e*4+2];
                *(colors + col_offset + cio++) = 1.0;

                *(colors + col_offset + cio++) = palette[e*4]  ;
                *(colors + col_offset + cio++) = palette[e*4+1];
                *(colors + col_offset + cio++) = palette[e*4+2];
                *(colors + col_offset + cio++) = 1.0;

                res_offset += 6;
                col_offset += 8;
        }
    }

    free(data);
    free(coords);
    free(coord_param);
}

// Callbacks
// intialize callback that gets called once for intialization
void
_init_gl(Evas_Object *obj)
{
   int w, h;
   GLData *gld = evas_object_data_get(obj, "graph_gld");
   Evas_GL_API *gl = gld->glapi;

   elm_glview_size_get(obj, &w, &h);

   if (!gld->initialized)
     {
        if (!init_shaders(gld))
          {
             printf("Error Initializing Shaders\n");
             return;
          }

        gld->vertices = malloc(sizeof(float));
        gld->colors = malloc(sizeof(float));
        gld->vertices_size = 1;
        gld->colors_size = 1;

        gl->glGenBuffers(1, &gld->vertexID);
        gl->glBindBuffer(GL_ARRAY_BUFFER, gld->vertexID);
        gl->glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

        gl->glGenBuffers(1, &gld->colorID);
        gl->glBindBuffer(GL_ARRAY_BUFFER, gld->colorID);
        gl->glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW);

        _generate_vertices(gld);

        GLuint test = 0;
        //gl->glGenBuffers(1, &gld->vertexID2);
        gl->glGenBuffers(1, &test);
        //gl->glBindBuffer(GL_ARRAY_BUFFER, gld->vertexID2);
        gl->glBindBuffer(GL_ARRAY_BUFFER, test);
        gl->glBufferData(GL_ARRAY_BUFFER, gld->vertices_size, gld->vertices, GL_STATIC_DRAW);

        gl->glGenBuffers(1, &gld->colorID2);
        gl->glBindBuffer(GL_ARRAY_BUFFER, gld->colorID2);
        gl->glBufferData(GL_ARRAY_BUFFER, gld->colors_size, gld->colors, GL_STATIC_DRAW);

        gld->initialized = EINA_TRUE;
     }
}

// draw callback is where all the main GL rendering happens
void
_draw_gl(Evas_Object *obj)
{
   Evas_GL_API *gl = elm_glview_gl_api_get(obj);
   GLData *gld = evas_object_data_get(obj, "graph_gld");
   if(!gld) return;
   int w, h;
   int r, g, b, a;
   //scale
   double scalex = 1.0; //elm_slider_value_get(gld->slx);
   double scaley = gld->ctx->graph_zoom; //elm_slider_value_get(gld->sly);
   double scalez = 1.0; //elm_slider_value_get(gld->slz);

   r = 10; g = 10; b = 10; a = 255;

   elm_glview_size_get(obj, &w, &h);

   gl->glClearDepthf(1.0f);
   gl->glClearColor(r / 255.0, g / 255.0, b / 255.0, a / 255.0);
   //gl->glEnable(GL_CULL_FACE);

   gl->glViewport(0, 0, w, h);
   gl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   gl->glUseProgram(gld->program);

   gl->glEnableVertexAttribArray(gld->positionLoc);
   gl->glBindBuffer(GL_ARRAY_BUFFER, gld->vertexID);
   gl->glVertexAttribPointer(gld->positionLoc, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);

   gl->glEnableVertexAttribArray(gld->colorLoc);
   gl->glBindBuffer(GL_ARRAY_BUFFER, gld->colorID);
   gl->glVertexAttribPointer(gld->colorLoc, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);

 //  gl->glUniformMatrix4fv(gld->mvpLoc, 1, GL_FALSE, gld->mvp);
   gl->glEnable(GL_BLEND);
   gl->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

   gl->glDrawArrays(GL_TRIANGLES, 0, 6);


    _generate_vertices(gld);

    gl->glDeleteBuffers(1, &gld->vertexID2);
    gl->glGenBuffers(1, &gld->vertexID2);
    gl->glBindBuffer(GL_ARRAY_BUFFER, gld->vertexID2);
    gl->glBufferData(GL_ARRAY_BUFFER, gld->vertices_size, gld->vertices, GL_STATIC_DRAW);

    gl->glDeleteBuffers(1, &gld->colorID2);
    gl->glGenBuffers(1, &gld->colorID2);
    gl->glBindBuffer(GL_ARRAY_BUFFER, gld->colorID2);
    gl->glBufferData(GL_ARRAY_BUFFER, gld->colors_size, gld->colors, GL_STATIC_DRAW);



   gl->glEnableVertexAttribArray(gld->positionLoc);
   gl->glBindBuffer(GL_ARRAY_BUFFER, gld->vertexID2);
   gl->glVertexAttribPointer(gld->positionLoc, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);

   gl->glEnableVertexAttribArray(gld->colorLoc);
   gl->glBindBuffer(GL_ARRAY_BUFFER, gld->colorID2);
   gl->glVertexAttribPointer(gld->colorLoc, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);

   customLoadIdentity(gld->model);
   gld->yangle += 0.2;
   customRotate(gld->model, gld->xangle, gld->yangle, gld->zangle);
   //scale
   customScale(gld->model, scalex, scaley, scalez);
   customMutlMatrix(gld->mvp, gld->view, gld->model);

   gl->glUniformMatrix4fv(gld->mvpLoc, 1, GL_FALSE, gld->mvp);
    PhexfileCoordinate* x_coord;
    PhexfileCoordinate* y_coord;
    x_coord = g_list_nth_data(phexfile_simulation_get_coordinates(gld->ctx->sim), 0);
    y_coord = g_list_nth_data(phexfile_simulation_get_coordinates(gld->ctx->sim), 1);
   unsigned int x_coord_steps = phexfile_coordinate_get_steps(x_coord);
   unsigned int y_coord_steps = phexfile_coordinate_get_steps(y_coord);
   unsigned int n_exc = g_list_length(phexfile_simulation_get_excitations(gld->ctx->sim));
   size_t gridsize = n_exc * (y_coord_steps-1) * (x_coord_steps-1) * 6;
   gl->glDrawArrays(GL_TRIANGLES, 0, gridsize);
   gl->glDrawArrays(GL_LINES, gridsize, n_exc * (y_coord_steps-1 + x_coord_steps-1) * 2);
   gl->glFlush();
}
